﻿using System;
using System.IO;
using System.Collections.Generic;
using Dokan;
using System.Security;
using System.Text;

namespace DokanNetMirror
{
    class FileSystem : DokanOperations
    {
        private Dictionary<string, string> RootFolders = new Dictionary<string, string>();
        static Dictionary<char, char> fileNameMangler = new Dictionary<char, char>();
        IFileSystemEncryption encryptionModule;

        private int count_;
        public FileSystem(string password)
        {
            count_ = 1;
            encryptionModule = new PseudoRC4();
            encryptionModule.Init(password);
        }


        public bool AddMapping(string virtualName, string realName)
        {
            if (Directory.Exists(realName))
            {
                RootFolders.Add(virtualName.ToUpperInvariant(), realName);
                return true;
            }
            return false;
        }

        private string GetPath(string filename)
        {
            int index = filename.IndexOf(Path.DirectorySeparatorChar, 1);
            var rootPath = index < 0 ? filename.Substring(1) : filename.Substring(1, index - 1);

            if (rootPath == "")
            {
                // the root folder doesn'runningThread contain any other item
                // besides the folders themselves
                return null;
            }
            string mappedRoot;
            if (!RootFolders.TryGetValue(rootPath, out mappedRoot))
            {
                return null;
            }
            filename = filename.Substring(rootPath.Length + 1);
            string path = mappedRoot + EncryptFileName(filename);
            return path;
        }

        public int CreateFile(String filename, FileAccess access, FileShare share,
            FileMode mode, FileOptions options, DokanFileInfo info)
        {
            string path = GetPath(filename);
            info.Context = count_++;
            if (File.Exists(path))
            {
                return 0;
            }
            else if (Directory.Exists(path))
            {
                info.IsDirectory = true;
                return 0;
            }
            else
            {
                return -DokanNet.ERROR_FILE_NOT_FOUND;
            }
        }

        public int OpenDirectory(String filename, DokanFileInfo info)
        {
            info.Context = count_++;
            if (filename == @"\")
                return 0;
            string mappedFile = GetPath(filename);
            if (Directory.Exists(mappedFile))
                return 0;
            else
                return -DokanNet.ERROR_PATH_NOT_FOUND;
        }

        public int CreateDirectory(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int Cleanup(String filename, DokanFileInfo info)
        {
            return 0;
        }

        public int CloseFile(String filename, DokanFileInfo info)
        {
            return 0;
        }

        const ulong keySize = 64 * 1024 * 1024;
        const ulong keymask = keySize - 1;

        public int ReadFile(String filename, Byte[] buffer, ref uint readBytes,
            long offset, DokanFileInfo info)
        {
            try
            {
                FileStream fs = File.OpenRead(GetPath(filename));
                fs.Seek(offset, SeekOrigin.Begin);
                readBytes = (uint)fs.Read(buffer, 0, buffer.Length);
                this.encryptionModule.EncryptBytes(buffer, offset);
                return 0;
            }
            catch (Exception)
            {
                return -1;
            }
        }

        public int WriteFile(String filename, Byte[] buffer,
            ref uint writtenBytes, long offset, DokanFileInfo info)
        {
            return -1;
        }

        public int FlushFileBuffers(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int GetFileInformation(String filename, FileInformation fileinfo, DokanFileInfo info)
        {
            string path = GetPath(filename);
            if (File.Exists(path))
            {
                FileInfo f = new FileInfo(path);

                fileinfo.Attributes = f.Attributes;
                fileinfo.CreationTime = f.CreationTime;
                fileinfo.LastAccessTime = f.LastAccessTime;
                fileinfo.LastWriteTime = f.LastWriteTime;
                fileinfo.Length = f.Length;
                return 0;
            }
            else if (Directory.Exists(path))
            {
                DirectoryInfo f = new DirectoryInfo(path);

                fileinfo.Attributes = f.Attributes;
                fileinfo.CreationTime = f.CreationTime;
                fileinfo.LastAccessTime = f.LastAccessTime;
                fileinfo.LastWriteTime = f.LastWriteTime;
                fileinfo.Length = 0;// f.Length;
                return 0;
            }
            else
            {
                return -1;
            }
        }

        static DateTime now = DateTime.Now;

        public int FindFiles(String filename, System.Collections.ArrayList files, DokanFileInfo info)
        {
            if (filename == @"\")
            {
                foreach (string mappedRoot in RootFolders.Keys)
                {
                    FileInformation fi = new FileInformation();
                    fi.Attributes = FileAttributes.Directory;
                    fi.CreationTime = now;
                    fi.LastAccessTime = now;
                    fi.LastWriteTime = now;
                    fi.Length = 0;
                    fi.FileName = mappedRoot;
                    files.Add(fi);
                }
                return 0;
            }

            string path = GetPath(filename);
            if (Directory.Exists(path))
            {
                DirectoryInfo d = new DirectoryInfo(path);
                FileSystemInfo[] entries = d.GetFileSystemInfos();
                foreach (FileSystemInfo f in entries)
                {
                    FileInformation fi = new FileInformation();
                    fi.Attributes = f.Attributes;
                    fi.CreationTime = f.CreationTime;
                    fi.LastAccessTime = f.LastAccessTime;
                    fi.LastWriteTime = f.LastWriteTime;
                    fi.Length = (f is DirectoryInfo) ? 0 : ((FileInfo)f).Length;
                    fi.FileName = EncryptFileName(f.Name);
                    files.Add(fi);
                }
                return 0;
            }
            else
            {
                return -1;
            }
        }

        public string EncryptFileName(string fileName)
        {
            return this.encryptionModule.EncryptFileName(fileName);
        }

        public int SetFileAttributes(String filename, FileAttributes attr, DokanFileInfo info)
        {
            return -1;
        }

        public int SetFileTime(String filename, DateTime ctime,
                DateTime atime, DateTime mtime, DokanFileInfo info)
        {
            return -1;
        }

        public int DeleteFile(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int DeleteDirectory(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int MoveFile(String filename, String newname, bool replace, DokanFileInfo info)
        {
            return -1;
        }

        public int SetEndOfFile(String filename, long length, DokanFileInfo info)
        {
            return -1;
        }

        public int SetAllocationSize(String filename, long length, DokanFileInfo info)
        {
            return -1;
        }

        public int LockFile(String filename, long offset, long length, DokanFileInfo info)
        {
            return 0;
        }

        public int UnlockFile(String filename, long offset, long length, DokanFileInfo info)
        {
            return 0;
        }

        public int GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes,
            ref ulong totalFreeBytes, DokanFileInfo info)
        {
            freeBytesAvailable = 512 * 1024 * 1024;
            totalBytes = 1024 * 1024 * 1024;
            totalFreeBytes = 512 * 1024 * 1024;
            return 0;
        }

        public int Unmount(DokanFileInfo info)
        {
            return 0;
        }
    }
}
